//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Execution
{
    using System;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.Runtime;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.eResearch.Common.Eventing;
    using Microsoft.Research.eResearch.Common.Linq;
    using Microsoft.Research.eResearch.Common.Validation;
    using Microsoft.Research.eResearch.RuntimeServices;
    using Microsoft.Research.eResearch.RuntimeServices.Blackboard;
    using Microsoft.Research.eResearch.RuntimeServices.Logging;
    using Microsoft.Research.eResearch.RuntimeServices.Monitoring;
    using Microsoft.Research.eResearch.RuntimeServices.Ontology;
    using Microsoft.Research.eResearch.RuntimeServices.Tracking;
    using Microsoft.Research.eResearch.RuntimeServices.WorkflowOutput;

    /// <summary>
    /// This class contains the common logic to execute the new workflow and paused workflow.
    /// </summary>
    internal abstract class WorkflowExecutor : IDisposable
    {
        /// <summary>
        /// Flag to indicate whether this object is disposed already.
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Initializes a new instance of the WorkflowExecutor class.
        /// </summary>
        /// <param name="jobId">Registry job id.</param>
        /// <param name="loadedAssemblies">Loaded assemblies collection.</param>
        /// <param name="workflowName">Name of the workflow.</param>
        protected WorkflowExecutor(Guid jobId, Collection<Assembly> loadedAssemblies, string workflowName, bool isLightWeightExecution)
        {
            // Create workflow runtime instance
            CreateWorkflowRuntime();

            // Add necessary workflow services
            AddWorkflowServices(jobId, loadedAssemblies, workflowName, isLightWeightExecution);

            // Start the runtime 
            Runtime.StartRuntime();
        }

        /// <summary>
        /// Raised whenever workflow runtime raises the terminate event.
        /// </summary>
        public event EventHandler<WorkflowTerminatedEventArgs> WorkflowTerminated;

        /// <summary>
        /// Raised whenever workflow runtime raises the completed event.
        /// </summary>
        public event EventHandler<WorkflowCompletedEventArgs> WorkflowCompleted;

        /// <summary>
        /// Gets a value indicating whether the workflow instance is started.
        /// </summary>
        public bool IsStarted { get; protected set; }

        /// <summary>
        /// Gets the workflow runtime instance.
        /// </summary>
        protected WorkflowRuntime Runtime { get; private set; }

        /// <summary>
        /// Gets or sets the workflow instance.
        /// </summary>
        protected WorkflowInstance Instance { get; set; }

        /// <summary>
        /// Creates a new instance of the workflow executor.
        /// </summary>
        /// <param name="jobId">Registry job id.</param>
        /// <param name="loadedAssemblies">Loaded assemblies collection.</param>
        /// <param name="workflowName">Name of the workflow.</param>
        /// <param name="xomlPath">Path of the workflow XOML.</param>
        /// <returns>Instance of the WorkflowExecutor.</returns>
        public static WorkflowExecutor CreateWorkflowExecutor(Guid jobId, Collection<Assembly> loadedAssemblies, string workflowName, string xomlPath, DataLayer.Connection conn, bool isLightWeightExecution)
        {
            // Validate arguments
            CustomValidator.AssertNotNull(jobId, "jobId");
            CustomValidator.AssertNotNull(loadedAssemblies, "loadedAssemblies");
            CustomValidator.AssertNotEmpty(workflowName, "workflowName");
            CustomValidator.AssertNotEmpty(xomlPath, "xomlPath");

            return new NewWorkflowExecutor(jobId, loadedAssemblies, workflowName, xomlPath, conn, isLightWeightExecution);
        }

        /// <summary>
        /// Dispose this object and releases all the managed resources it contains.
        /// </summary>
        public void Dispose()
        {
            ThrowIfDisposed();
            Runtime.StopRuntime();
            Runtime = null;
            GC.SuppressFinalize(this);
            disposed = true;
        }

        /// <summary>
        /// Executes the workflow instance.
        /// </summary>
        public abstract void Execute();

        /// <summary>
        /// Terminates the workflow instance.
        /// </summary>
        public virtual void Terminate(string errorMessage)
        {
            ThrowIfDisposed();
            Instance.Terminate(errorMessage);
        }

        /// <summary>
        /// Pauses the workflow instance.
        /// </summary>
        public virtual void Pause()
        {
            ThrowIfDisposed();
            Instance.Suspend("Paused by user");
        }

        /// <summary>
        /// Throws the ObjectDisposedException if operation is performed on the disposed object. 
        /// </summary>
        protected void ThrowIfDisposed()
        {
            if (disposed)
            {
                throw new ObjectDisposedException("WorkflowExecutor");
            }
        }

        /// <summary>
        /// Creates workflow runtime and attaches the events.
        /// </summary>
        private void CreateWorkflowRuntime()
        {
            Runtime = new WorkflowRuntime();
            Runtime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(WorkflowCompletedHandler);
            Runtime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(WorkflowTerminatedHandler);
        }

        /// <summary>
        /// Adds the custom runtime services to the runtime.
        /// </summary>
        /// <param name="jobId">Job id to be used by the runtime service.</param>
        /// <param name="loadedAssemblies">Loaded assemblies collection.</param>
        /// <param name="workflowName">Name of the workflow used by the runtime service.</param>
        private void AddWorkflowServices(Guid jobId, Collection<Assembly> loadedAssemblies, string workflowName, bool isLightWeightExecution)
        {
            Runtime.AddService(new JobExecutionService(jobId.ToString(), workflowName));
            if (!isLightWeightExecution)
            {
                Runtime.AddService(new MonitoringService());
                Runtime.AddService(new PropertyTrackingService());
                Runtime.AddService(new OntologyMappingService());
                Runtime.AddService(new BlackboardService());
            }

            Runtime.AddService(new WorkflowOutputSaveService());
            Runtime.AddService(new OutputTrackingService());

            TypeProvider typeProvider = new TypeProvider(Runtime);
            loadedAssemblies.ForEach(assembly => typeProvider.AddAssembly(assembly));

            // Add the workflow executor assembly also in order to get the Setter activity.
            typeProvider.AddAssembly(typeof(Setter).Assembly);
            Runtime.AddService(typeProvider);
        }

        /// <summary>
        /// This method is executed by the workflow runtime on terminate event.
        /// </summary>
        /// <param name="sender">Instance of the workflow runtime.</param>
        /// <param name="e">Terminated event argument.</param>
        private void WorkflowTerminatedHandler(object sender, WorkflowTerminatedEventArgs e)
        {
            WorkflowTerminated.Fire(this, e);
        }

        /// <summary>
        /// This method is executed by the workflow runtime on complete event.
        /// </summary>
        /// <param name="sender">Instance of the workflow runtime.</param>
        /// <param name="e">Completed event argument.</param>
        private void WorkflowCompletedHandler(object sender, WorkflowCompletedEventArgs e)
        {
            WorkflowCompleted.Fire(this, e);
        }
    }
}
